Modulo di scrittura software e Gestione Dati

Questo modulo ha lo scopo di introdurvi alle basi della programmazione ed alla sua applicazione al campo scientifico.

Nel corso di questo modulo useremo due linguaggi di programmazione di alto livello, python e matlab.

Oltre a questi linguaggi ne esistono moltissimi altri, tutti appropriati all'uso:

  • R
  • Julia
  • Mathematica
  • Scala
  • E tanti altri ancora

Vedremo anche nel corso delle lezioni vari concetti che vi torneranno utili qualsiasi sarà il linguaggio con cui deciderete di lavorare.

In particolare oggi inizieremo discutendo il controllo di versione.

Ma prima, che cosa sono i linguaggi di alto livello, e perché vale la pena usarli?

I linguaggi di programmazione ad alto livello

Il termine "linguaggio di alto livello" è una forma colloquiale, non definita in senso formale, ma sono presenti varie proprietà che li accumunano.

Lo scopo comune è rendere molto più semplice lo sviluppo di codice. Tutto il tempo che non passo a cercare di ricordare come inizializzare un puntatore, lo posso spendere a rendere le mie analisi più interessanti

  • nascondono la gestione diretta delle risorse del computer (memoria, puntatori, etc...)
  • forniscono una sintassi più intuitiva ed espressiva
  • arrivano corredati di molte funzioni e strutture dati di uso comune
  • rendono molto semplice installare librerie aggiuntive che ne espandono le funzionalità

vediamo un esempio che possa aiutare a capire che cosa intendo, usando il Python

In [ ]:
import os
filenames = [filename for filename in os.listdir('.') if filename.endswith('.txt')]
for filename in filenames:
    os.rename(filename, filename.replace('.txt', '.csv'))

Vedete che il codice, anche senza commenti, è chiaro e leggibile, ed esprimo dei concetti molto complicati in poche linee di codice

Correttezza, ripetibilità (e riproducibilità), auditing

Oggi come oggi chi fa ricerca deve per forza scrivere del codice.

Ci viene insegnato a scrivere codice ma non a prendercene cura.

Un modo di dire comune nel campo dello sviluppo è:

Scrivi il tuo codice come se la prossima persona a doversene occupare fosse uno psicopatico che sa dove abiti.

Considerando il numero di progetti che lo scienziato medio deve seguire al giorno d'oggi, lo psicopatico in questione potreste essere voi!

  • CORRETTEZZA: essere sicuri che il vostro codice faccia esattamente quello che pensate; qualsiasi modifica facciate, volete essere certi di non aver introdotto errori. Nel caso avvenisse, volete poter tornare indietro.
  • RIPETIBILITÀ - I: volete essere in grado di ripetere un'analisi ottenendo gli stessi risultati a distanza di tempi anche lunghi. Questo vuol dire tenere traccia di quali siano i requisiti del vostro software, di come si usa, su quali dati e con quali parametri.
  • RIPETIBILITÀ - II: permettere a qualcun altro di fare lo stesso, possibilmente senza che voi siate li fisicamente presenti a spiegare passo per passo.
  • RIPRODUCIBILITÀ: permettere ad altri di fare lo stesso, e di testare il vostro codice (ed in generale le vostre idee) sul altri dati ed altri casi rispetto a quelli da voi esaminati.
  • AUDITING: mantenere la storia del progetto, per sapere cosa è stato fatto, quando e perché. Queste sia per mantenere la comprensione acquisita nel tempo che per permettere a dei revisori esterni di verificare quello che avete fatto.

Cosa è necessario per avere del codice salubre?

  • controllo di versione
  • documentazione
  • procedure di test
  • automazione delle procedure
  • design sensato della pipeline di lavoro

Durante questo corso cercheremo non solo di insegnarvi la programmazione di alto livello, ma anche come gestire i vostri progetti in modo da limitare il numero di momenti di terrore, disperazione e sconforto che avreste altrimenti.

Se pensate che stia scherzando, immaginate le seguenti situazioni:

  • la vostra simulazione ha girato per 36 ore, e fallisce all'ultimo step. Non sapete come recuperare, dovete ripartire da capo, senza essere sicuri di quale sia l'errore
  • durante l'edit della vostra tesi cancellate un paragrafo (o una intera sezione) e non ve ne accorgete
  • vi chiedono di modificare una figura per un articolo, ma il codice per generarla deve far girare di nuovo la famosa simulazione di 36 ore
  • dovete dedicarvi agli esami per 6-7 mesi, quando è il momento di riprendere in mano il progetto non ricordate più cosa aveste sperimentato e cosa era ancora nella lista delle cose da fare
  • il vostro progamma richiede una serie di step ben precisi per eseguire correttamente. Il gatto vi nasconde il foglietto dove li avete appuntati sotto il divano
  • avete tenuto la documentazione del vostro progetto, ma nell'ultimo backup vi siete dimenticati di copiare l'ultima versione ed ora avete la documentazione ed il codice che non coincidono.
  • fate una modifica al vostro codice, e vi rendete conto durante la presentazione al professore che un passaggio era sbagliato e vi da dei risultati assurdi
  • prendete in mano il codice di qualcuno, e non avete la più pallida idea del perchè una linea di codice sia lì, ma non potete cambiarla perchè non capite se è essenziale per il codice.

Questi sono solo alcuni esempi, assolutamente visti capitare nella vita reale, fonti di stress facilmente evitabile.

Le procedure che vi insegneremo non possono ovviamente contrastare la sfortuna cieca, ma possono arginarne gli effetti nocivi ed evitare il disastro totale.

E sono tutte abbastanza semplici perché usarle non sia uno sforzo immane!

il concetto dietro questi esempi e questi strumenti è la corretta gestione dei metadata (dati a proposito dei dati).

Considerate un pezzo di programma come quello di prima. Il nome delle variabili è assolutamente arbitrario, e non influenza in nessun modo il risultato del programma, ma se cambiassi i nomi a caso il codice, seppure corretto, sarebbe incomprensibile, difficile da modificare e in poco tempo dimenticherei completamente il suo scopo d'essere.

Nel codice di solito si usano nomi coerenti e commenti, ma c'è molto altro di cui possiamo tenere traccia se ne siamo consci.

Controllo di versione distribuito e Code Source Management

Avete mai avuto diverse copie di un file, chiamate doc_v1, doc_v2 e così via?

Bene, quello è un controllo di versione manuale. Grazie al computer possiamo fare di meglio.

Possiamo tenere traccia di tutto quello che succede, tornare indietro ad un momento qualsiasi, vedere le differenze fra due versioni successive ed anche tenere "universi paralleli" di versioni alternative.

I programmi che permettono di farlo sono i Sistemi di Controllo di Versione.

Questi sono poi affiancati da sistemi di Management del Codice Sorgente, che oltre a tenere traccia della versione permettono anche di tracciare i bug e la documentazione.

Fossil è una semplice soluzione integrata che vi fornisce tutto questo in un unico pacchetto.

È già estremamente utile per lavorare da soli, e permette di usare lo stesso strumento per collaborare, coordinando il lavoro di varie persone che lavorano allo stesso progetto, online ed offline.

Le collaborazioni avvengono tramite un server centrale che fa da punto di scambio.

Potete anche ospitarlo su di un vostro computer, il server è lo stesso programma che avete già scaricato.

Ogni modifica che farete sarà automaticamente registrata sul server in automatico, e passata agli altri quando si collegheranno.

cit:

se vale la pena di essere fatto, vale la pena di essere messo sotto controllo di versione

si può scaricare dal suo sito web: Fossil-SCM.

È un programma free ed open source, cross-platform e contenuto in un unico file, quindi è molto facile da spostare in giro.

Il vostro repository è contenuto in un unico file, un database che contiene tutte le informazioni rilevanti (new).

Fare il backup dell'intero repository con TUTTE le informazioni è semplicemente copiare il file in giro. basta tenerlo nella cartella di sincronizzazione di dropbox ed il problema è risolto.

Potete estrarne l'ultima versione in una cartella (open), modificarla e, quando siete soddisfatti del risultato, inserirla come una versione successiva (commit).

Potete usare il controllo di versione per qualsiasi file text-based che volete, non soltanto il codice.

Ad esempio tenere la propria tesi ed il testo dei propri articoli sotto controllo di versione è generalmente una buona idea.

Ogni volta che vi allontanate dall'usare file di teste, perdete tutta la potenza di questi sistemi di controllo, ed è un vantaggio insostituibile!

In [75]:
!fossil init mytestrepo.fossil
project-id: 08090f56228b95bd9372d6d15fc632dcaaa91edb
server-id:  0f917f69c95b135b5e526bc91e8028ce98b09083
admin-user: enrico (initial password is "b16604")
In [76]:
%ls
mytestrepo.fossil
In [77]:
%mkdir -p working_directory
%cd working_directory
%pwd
/home/enrico/lavoro/fossilexample/working_directory
Out[77]:
'/home/enrico/lavoro/fossilexample/working_directory'
In [78]:
!fossil open ../mytestrepo.fossil
project-name: <unnamed>
repository:   /home/enrico/lavoro/fossilexample/working_directory/../mytestrepo.fossil
local-root:   /home/enrico/lavoro/fossilexample/working_directory/
config-db:    /home/enrico/.fossil
project-code: 08090f56228b95bd9372d6d15fc632dcaaa91edb
checkout:     e17a3d4a58fef68da694289aa32334d82bde5234 2017-02-27 20:27:38 UTC
tags:         trunk
comment:      initial empty check-in (user: enrico)
check-ins:    1
In [79]:
%%writefile test.txt
This is going to be my documentation
Writing test.txt
In [80]:
!fossil status
repository:   /home/enrico/lavoro/fossilexample/working_directory/../mytestrepo.fossil
local-root:   /home/enrico/lavoro/fossilexample/working_directory/
config-db:    /home/enrico/.fossil
checkout:     e17a3d4a58fef68da694289aa32334d82bde5234 2017-02-27 20:27:38 UTC
tags:         trunk
comment:      initial empty check-in (user: enrico)
In [81]:
!fossil extra
test.txt
In [82]:
!fossil add test.txt
ADDED  test.txt
In [83]:
!fossil status
repository:   /home/enrico/lavoro/fossilexample/working_directory/../mytestrepo.fossil
local-root:   /home/enrico/lavoro/fossilexample/working_directory/
config-db:    /home/enrico/.fossil
checkout:     e17a3d4a58fef68da694289aa32334d82bde5234 2017-02-27 20:27:38 UTC
tags:         trunk
comment:      initial empty check-in (user: enrico)
ADDED      test.txt
In [84]:
!fossil commit -m "il primo file di un grande progetto!"
New_Version: 2dab9716e96fab6bcf87e8d8e17b3a672bfea4eb
In [85]:
!fossil status
repository:   /home/enrico/lavoro/fossilexample/working_directory/../mytestrepo.fossil
local-root:   /home/enrico/lavoro/fossilexample/working_directory/
config-db:    /home/enrico/.fossil
checkout:     2dab9716e96fab6bcf87e8d8e17b3a672bfea4eb 2017-02-27 20:34:25 UTC
parent:       e17a3d4a58fef68da694289aa32334d82bde5234 2017-02-27 20:27:38 UTC
tags:         trunk
comment:      il primo file di un grande progetto! (user: enrico)

a questo punto potete vedere cosa succere lanciando il comando

fossil ui

timeline

timeline

file list

file list

In [86]:
%%writefile test.txt
This is going to be my documentation
I'm adding more lines: the more, the merrier!
Overwriting test.txt
In [87]:
!fossil status
repository:   /home/enrico/lavoro/fossilexample/working_directory/../mytestrepo.fossil
local-root:   /home/enrico/lavoro/fossilexample/working_directory/
config-db:    /home/enrico/.fossil
checkout:     2dab9716e96fab6bcf87e8d8e17b3a672bfea4eb 2017-02-27 20:34:25 UTC
parent:       e17a3d4a58fef68da694289aa32334d82bde5234 2017-02-27 20:27:38 UTC
tags:         trunk
comment:      il primo file di un grande progetto! (user: enrico)
EDITED     test.txt
In [89]:
!fossil diff test.txt
Index: test.txt
==================================================================
--- test.txt
+++ test.txt
@@ -1,1 +1,2 @@
 This is going to be my documentation
+I'm adding more lines: the more, the merrier!

In [90]:
!fossil commit -m "espansa la documentazione"
New_Version: 1856bb82f3515646637061c6e062c117fc646192

nuova timeline

timeline nuova

approfondimenti sull'edit

info edit

mi sono accorto di aver fatto un errore!

come posso rimediare?

Per prima cosa, lascio traccia della decisione: apro un ticket.

preparazione del ticket

ticket preparation

la lista dei ticket

ticket list

In [95]:
!fossil help revert
Usage: fossil revert ?-r REVISION? ?FILE ...?

Revert to the current repository version of FILE, or to
the version associated with baseline REVISION if the -r flag
appears.

If FILE was part of a rename operation, both the original file
and the renamed file are reverted.

Revert all files if no file name is provided.

If a file is reverted accidently, it can be restored using
the "fossil undo" command.

Options:
  -r REVISION    revert given FILE(s) back to given REVISION

See also: redo, undo, update
In [97]:
!fossil revert -r  2dab9716e9 test.txt
REVERT   test.txt
 "fossil undo" is available to undo changes to the working checkout.
In [98]:
!fossil status
repository:   /home/enrico/lavoro/fossilexample/working_directory/../mytestrepo.fossil
local-root:   /home/enrico/lavoro/fossilexample/working_directory/
config-db:    /home/enrico/.fossil
checkout:     1856bb82f3515646637061c6e062c117fc646192 2017-02-27 20:42:51 UTC
parent:       2dab9716e96fab6bcf87e8d8e17b3a672bfea4eb 2017-02-27 20:34:25 UTC
tags:         trunk
comment:      espansa la documentazione (user: enrico)
EDITED     test.txt
In [99]:
!fossil diff test.txt
Index: test.txt
==================================================================
--- test.txt
+++ test.txt
@@ -1,2 +1,1 @@
 This is going to be my documentation
-I'm adding more lines: the more, the merrier!

In [100]:
!fossil commit -m "riportato l'ordine nel repository!"
New_Version: 488479276df60e9fd055d5dbe07ee6c37b30beb3

ora non mi rimane che chiudere il ticket e continuare il mio lavoro

branching

una funzionalità molto potente per il controllo di versione è la possibilità di fare branching.

Questo significa che nel mio database esistono nello stesso momento due versioni diverse dello stesso progetto, e posso passare da un all'altra senza problemi.

Un grosso vantaggio del modello di DVCS di fossil è che posso usare diverse cartelle per diversi branch, ed avere il tutto sincronizzato!

un caso tipico di branching

avete appena raggiunto un risultato pubblicabile con le vostre analisi, e volete prepararvi per pubblicarle.

Questo significa che non altererete più il codice in modo sostanziale (volete mantenere l'integrità dei risultati), ma magari aggiusterete i grafici ed i report.

Allo stesso momento, i vostri collaboratori vi chiedono di fare delle modifiche piuttosto importanti del codice. Come potete fare?

Usando due diverse branch siete in grado di dividere il lavoro senza fare confusione, ma mantenendo tutto sempre sotto controllo (e con la possibilità di far passare i risultati da un branch all'altro con revert).

In [102]:
!fossil help checkout
Usage: fossil checkout ?VERSION | --latest? ?OPTIONS?
   or: fossil co ?VERSION | --latest? ?OPTIONS?

Check out a version specified on the command-line.  This command
will abort if there are edited files in the current checkout unless
the --force option appears on the command-line.  The --keep option
leaves files on disk unchanged, except the manifest and manifest.uuid
files.

The --latest flag can be used in place of VERSION to checkout the
latest version in the repository.

Options:
   --force           Ignore edited files in the current checkout
   --keep            Only update the manifest and manifest.uuid files
   --force-missing   Force checkout even if content is missing

See also: update

per comodità faremo il cambio di branch nella stessa directory, ma in generale creeremmo una seconda directory, apriremmo il repository e faremmo il checkout della versione che ci interessa.

In questo caso, ritorniamo allo stato iniziale del repository.

In [103]:
!fossil checkout e17a3d4a58

il repository è vuoto come l'abbiamo lasciato due commit fa.

In [104]:
ls
In [105]:
%%writefile test.txt
this is an alternative history
Writing test.txt
In [108]:
!fossil add test.txt
ADDED  test.txt
In [109]:
!fossil commit -m "nuova storia"
would fork.  "update" first or use --allow-fork.
In [110]:
!fossil commit --allow-fork -m "nuova storia"
New_Version: 5d56786fcd0396b860365fe6ea6f01c8186818cb
**** warning: a fork has occurred *****

la nuova timeline

timeline with branch

aggiusto le proprietà del nuovo branch

timeline with branch

ecco come appare la mia timeline ora

timeline with branch

notate che viene registrato anche il cambiamento di nome del branch!

Un'altra operazione molto comune è rinominare o eliminare un file.

Un comportamento curioso di fossil è che registra il cambiamento ma non lo attua: dovete esplicitamente rinominare il file dalla linea di comando

In [128]:
!fossil mv test.txt test2.txt
RENAME test.txt test2.txt
In [129]:
!ls
test.txt
In [130]:
!mv test.txt test2.txt
In [131]:
!fossil status
repository:   /home/enrico/lavoro/fossilexample/working_directory/../mytestrepo.fossil
local-root:   /home/enrico/lavoro/fossilexample/working_directory/
config-db:    /home/enrico/.fossil
checkout:     5d56786fcd0396b860365fe6ea6f01c8186818cb 2017-02-27 21:23:41 UTC
parent:       e17a3d4a58fef68da694289aa32334d82bde5234 2017-02-27 20:27:38 UTC
tags:         experiment
comment:      nuova storia (user: enrico)
RENAMED    test2.txt
In [132]:
!fossil commit -m "cambiato il nome del file"
New_Version: 5728ff85e15a79f162bb955aa4a0638967aebe0c
In [149]:
!rm test2.txt
In [151]:
!fossil rm test2.txt
DELETED test2.txt
In [152]:
%%writefile test3.txt
crazy experiments!
Writing test3.txt
In [153]:
!fossil add test3.txt
ADDED  test3.txt
In [154]:
!fossil commit -m "ultimo commit del branch sperimentale"
New_Version: 47cc19ae1f35b62a524f2b5ddc4e45d96d6b2bc7

Ora, visto che ci piace renderci la vita complicata, vogliamo unire i due branch.

Questo può servire quando stiamo facendo delle modifiche sperimentali ad una parte della nostra analisi e non vogliamo includerlo fintanto che non siamo sicuri che stia funzionando correttamente.

Per fare questo dobbiamo usare il comando merge, ma prima dobbiamo metterci nella linea principale che vogliamo mantenere (visto che l'altra poi sparirà) con un checkout.

Questo risulta molto più facile se avete due cartelle separate. Se state usando la stessa, controllate due volte prima di fare il checkout!

In [155]:
!fossil checkout 488479276d
test.txt
In [157]:
!fossil merge 47cc19ae1f --dry-run
ADDED test3.txt
REMINDER: this was a dry run - no files were actually changed.
In [158]:
!fossil merge 47cc19ae1f
ADDED test3.txt
 "fossil undo" is available to undo changes to the working checkout.
In [159]:
!fossil status
repository:   /home/enrico/lavoro/fossilexample/working_directory/../mytestrepo.fossil
local-root:   /home/enrico/lavoro/fossilexample/working_directory/
config-db:    /home/enrico/.fossil
checkout:     488479276df60e9fd055d5dbe07ee6c37b30beb3 2017-02-27 20:56:16 UTC
parent:       1856bb82f3515646637061c6e062c117fc646192 2017-02-27 20:42:51 UTC
tags:         trunk
comment:      riportato l'ordine nel repository! (user: enrico)
ADDED_BY_MERGE test3.txt
MERGED_WITH 47cc19ae1f35b62a524f2b5ddc4e45d96d6b2bc7
In [160]:
!fossil commit -m "le due storie sono finalmente unite!"
New_Version: cb5317076a540e3c4799ff184d509de665d47ddb

la versione post-sperimentazione

timeline with branch

full disclosure

Il file che abbiamo modificato all'inizio, test.txt, dava problemi per il merge perchè creato indipendentemente nei due branch. L'unica soluzione che ho trovato è stata gestire il merge a mano, in pratica creare un nuovo file da zero ed eventualmente riempirlo con il contenuto del vecchio.

In [ ]: